#include "GLGraphicsDevice.h"

#include "VertexDelclaration/GlVertexDeclaration.h"
#include "ShaderPrograms/GlSLShaderProgram.h"
#include "ShaderPrograms/CgProgram.h"
#include  "GlVertexBuffer.h"
#include  "GlIndexBuffer.h"
#include  "Textures/GlTexture2D.h"
#include  "Textures/GlCubeTexture.h"
namespace gengine
{
	namespace kernel
	{      
			GlGraphicsDevice::GlGraphicsDevice	(IGraphicsDevice::DeviceParameters &dp)
			{
				

				currentContext   = 0;
	            currentWindow    = 0;
				resourceContext  = 0;
				
				resourceWindow   = 0;
				
				
				glMinorVersion =  dp.getMinorVersion();
				glMadjorVersion = dp.getMadjorVersion();
				
				resourceWindow  = AddWindow("simple window","class",0,0,dp.getDepthBitsPerPixel(),dp.getBitsPerPixel(),false);
				resourceContext = new OGLRenderContext(glMinorVersion,glMadjorVersion);
				resourceContext->Create(resourceWindow->GetHdc());
				resourceWindow->Hide();
				ECreateWindow("OpenGLWindow","windclass",dp);
			    SetWindow("OpenGLWindow");
			}

			
			GlGraphicsDevice::~GlGraphicsDevice	()
			{
				
				Clear      ();
			}

			void	GlGraphicsDevice::GetDisplayModes			()
			{
				videoModes.clear();	
				DEVMODE	devMode;

			
				for ( int i = 0; EnumDisplaySettings(NULL, i, &devMode); i++ )
				{
					IGraphicsDevice::DeviceParameters mode(devMode.dmPelsWidth, devMode.dmPelsHeight, devMode.dmBitsPerPel, devMode.dmDisplayFrequency ) ;
					videoModes.push_back(mode);
					
				}
			}

			Window	*	GlGraphicsDevice::ECreateWindow	( const char* wndTitle, const char* wndClassName, IGraphicsDevice::DeviceParameters &dp  ) 
			{
				Window * newWnd = 0;
	            OGLRenderContext * newContext = 0;

				size_t numModes = videoModes.size();
				if(numModes==0)
				{
					GetDisplayModes ();
					numModes = videoModes.size();
				}

				bool isCurrent = false;
				string WndName = wndTitle;
			    string WndClassName = wndClassName;
				
				for(unsigned i=0; i<videoModes.size(); i++)
				{
					if(currentWindow && currentContext)
					{
						if(strcmp(currentWindow->GetWindowName(),wndTitle))
						{
							isCurrent = true;
						}
					}
					
					if(dp == videoModes[i])
					{
						map<string,Window*>::iterator WI = windows.find(WndName);
			            map<string,OGLRenderContext*>::iterator RCI = GLContexts.find(WndName);

						if(WI!=windows.end() && RCI!=GLContexts.end())
						{
							delete RCI->second;
							GLContexts.erase(RCI);
							DeleteWindow(WndName);
						}

						newWnd = AddWindow(WndName.c_str(),WndClassName.c_str(),dp.getWidth(),dp.getHeight(),dp.getDepthBitsPerPixel(),dp.getBitsPerPixel(),dp.isFullScreen());
			            newContext = new OGLRenderContext(glMinorVersion,glMadjorVersion);
						try
						{
							newContext->Create(newWnd->GetHdc());
							newContext->ShareContext(*resourceContext);
							newContext->MakeCurrent(newWnd->GetHdc());
						}

						catch(...)
						{
						}

						GLContexts[WndName] = newContext;
						
						
						GlInit();
						if(isCurrent)
				            SetWindow(WndName);

						if(currentContext && currentWindow)
						{
							currentContext->MakeCurrent(currentWindow->GetHdc());
							GlobalRendererState = currentContext->GetRenderState();
						}
						else
						{
							wglMakeCurrent(NULL,NULL);
						}
			
			       return newWnd;

					}
				}

				return 0;
				
			}

			bool        GlGraphicsDevice::SetWindow       (string wndName)
			{
				map<string,Window*>::iterator WI = windows.find(wndName);
				map<string,OGLRenderContext*>::iterator RCI = GLContexts.find(wndName);
				if(WI!=windows.end() && RCI!=GLContexts.end())
				{
					currentWindow = WI->second;
					currentContext = RCI->second;
					currentContext->MakeCurrent(currentWindow->GetHdc());
				    GlobalRendererState = currentContext->GetRenderState();
					currentWindow->Activate();
				
					
					return true;
				}
	                return false;
			}

			void					         GlGraphicsDevice:: SetRendererContext	( RenderContext * context)
			{
				map<string,OGLRenderContext*>::iterator RCI = GLContexts.find(currentWindow->GetWindowName());

				GLContexts[currentWindow->GetWindowName()] = static_cast<OGLRenderContext*>(context);

				currentContext = static_cast<OGLRenderContext*>(context);
				currentContext->MakeCurrent(currentWindow->GetHdc());
				
				Resize ( currentVideoMode.getWidth(), currentVideoMode.getHeight() );
				GlobalRendererState = currentContext->GetRenderState();
				currentWindow->Activate();
				
			}

			

			Window	*	GlGraphicsDevice::AddWindow	( const char* wndTitle, const char* wndClassName, int w,int h, int dpp, int pixelBits,bool isFullS) 
			{
				map<string,Window*>::iterator WI = windows.find(wndTitle);
				if(WI!=windows.end())
				{
					
				}

				Window * wnd = new Window(wndTitle,wndClassName,w,h,dpp,pixelBits,isFullS);
				if(wnd)
				{
					windows[wndTitle] = wnd;
				}
				return wnd;
			}

			IShaderProgram*	GlGraphicsDevice::CreateShader(ShaderProgramType type )
			{
				if(type == GLSL_SHADER)
				{
				   return new GlShaderProgram();
				}
				else if(type == CG_SHADER)
				{
					return new CgProgram();
				}
				return 0;
			}

			IVertexBuffer*	GlGraphicsDevice::CreateVertexBuffer( UINT size,int typeSize)
			{
				return new GLVertexBuffer(size,typeSize);
				
			}
		   
			IIndexBuffer *	GlGraphicsDevice::CreateIndexBuffer( ) 
			{
				return new GlIndexBuffer();
				
			}

			VertexDeclaration *GlGraphicsDevice::CreateVertexDeclaration( )   
			{
				return new GlVertexDeclaration();
				
			}

			ITexture*		GlGraphicsDevice::CreateTexture(  Vector2D &size, TextureCreationType tCreatType, TextureInternalFormat frmt, int miplevels)
			{
				if(tCreatType  == Tex2D)
				{
					return new GlTexture2D(size.x,size.y,frmt,miplevels);
					
				}
				else if(tCreatType  == CubeTexture)
				{
					return new  GlCubeTexture(size.x,size.y,frmt,miplevels);
					
				}
				return 0;
			}

			void		GlGraphicsDevice::SetVertexDeclaration( VertexDeclaration* vertDecl)
			{
				if(vertDecl!=0)
				{
				     for(int i = 0; i <  vertDecl->GetArribsSize(); ++i)
					 {
					GlVertexAttribute* attrib = dynamic_cast<GlVertexDeclaration*>(vertDecl)->GetVertexAttribs(i);

					 glVertexAttribPointerARB(attrib->attribId , attrib->numComponents, attrib->type, GL_FALSE,attrib->typeSize,  attrib->pointer);
		             glEnableVertexAttribArrayARB(attrib->attribId );
					 }
				}
			}
		    void		GlGraphicsDevice::SetSampler( Sampler* smp )
			{
				if(smp!=0)
				{   int target = smp->GetTexTarget();
				    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, smp->GetMagFilter());
                    glTexParameteri(target, GL_TEXTURE_MIN_FILTER, smp->GetMinFilter());
        
		
					glTexParameteri(target, GL_TEXTURE_WRAP_S, smp->GetWrapS());
					glTexParameteri(target, GL_TEXTURE_WRAP_T, smp->GetWrapT());
					glTexParameteri(target, GL_TEXTURE_WRAP_R, smp->GetWrapR());
                    glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, smp->GetAnisoLevel());
				}
			}

			void       GlGraphicsDevice::SetShader(IShaderProgram*program,bool use)
			{
				if(program!=0)
				{
					if(program->GetProgramType() == GLSL_SHADER)
					{     
						 if(use)
						 {
						  glUseProgramObjectARB ( dynamic_cast<GlShaderProgram*>(program)->GetProgramObject() );
						 }
						 else
						 {
						  glUseProgramObjectARB ( 0 );
						 }
					}
					else if(program->GetProgramType() == CG_SHADER)
					{
						if(use)
						{
						cgGLBindProgram ( dynamic_cast<CgProgram*>(program)->GetProgramObject() );
						}
						else
						{
							cgGLBindProgram ( 0 );
						}
					}
				}
			}
			
			void	GlGraphicsDevice::	SetTexture( ITexture* tex)
			{
				if(tex !=0)
				{
				glBindTexture(tex->GetTextureType(), tex->GetTextureId());
				}
			}

			 void		GlGraphicsDevice::SetVertexBuffer( IVertexBuffer*buf )
			 {
				 if(buf)
				 glBindBufferARB(GL_ARRAY_BUFFER, buf->GetId());
			 }
		   
			 void		GlGraphicsDevice::SetIndexBuffer( IIndexBuffer*buf )  
			 {
				 if(buf)
				 glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, buf->GetId());
			 }

			  void		GlGraphicsDevice::SetRenderTarget( ITexture* )
			  {
			  }
	    
			void         GlGraphicsDevice::DeleteWindow(const string& wndName) 
			{
				map<string,Window*>::iterator WI = windows.find(wndName);
				if(WI!=windows.end())
				{
					delete WI->second;
					windows.erase(WI);
					return;
				}
			}

			void         GlGraphicsDevice::Resize(int w , int h)
			{
				if(camera!=0)
				{
				camera->setViewSize(w,h,camera->getFov());
				}
			}
			
			void GlGraphicsDevice::SetCamera(Camera *cam)
			{
				camera = cam;
			}
		
			
			
			const char *	GlGraphicsDevice::GetVendor() const 
			{
				return (char *) glGetString ( GL_VENDOR );
			}
	      
			const  char *	GlGraphicsDevice::GetRenderer() const 
			{
				return (char *) glGetString ( GL_RENDERER );
			}
	      
		   const char *	GlGraphicsDevice::GetVersion() const 
			{
				return (char *) glGetString ( GL_VERSION );
			}

			
			void		GlGraphicsDevice::BeginScene ()
			{
				ClearBuffers(clearFlags);
				
			}
		   
			void		GlGraphicsDevice::EndScene   ()
			{
				currentWindow->SwapBuffer();
			}
			
			void        GlGraphicsDevice::ClearBuffers(ClearType type)
			{
			
				  if(type.Color && type.StencilBuffer && type.ZBuffer)
				  {
					glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
				  }
				  else if(type.Color && type.StencilBuffer)
				  {
					  glClear( GL_COLOR_BUFFER_BIT |GL_STENCIL_BUFFER_BIT );
				  }
				  else if(type.Color && type.ZBuffer)
				  {
					  glClear( GL_COLOR_BUFFER_BIT |GL_DEPTH_BUFFER_BIT );
				  }

				   else if(type.StencilBuffer && type.ZBuffer)
				  {
					  glClear( GL_STENCIL_BUFFER_BIT |GL_DEPTH_BUFFER_BIT );
				  }
				  else
				  {
					  if(type.Color)
						  glClear( GL_COLOR_BUFFER_BIT);

					  if(type.StencilBuffer)
						  glClear( GL_STENCIL_BUFFER_BIT);
					 
					  if(type.ZBuffer)
						  glClear( GL_DEPTH_BUFFER_BIT);

				  }


			}

			void GlGraphicsDevice::SetClearFlags(ClearType *type)
			{
				clearFlags.Color         = type->Color;
				clearFlags.ZBuffer       = type->ZBuffer;
				clearFlags.StencilBuffer = type->StencilBuffer;
			}
			void		GlGraphicsDevice::Draw       (IIndexBuffer*buf)
			{
				glDrawElements(GL_TRIANGLES, buf->Size(), GL_UNSIGNED_INT, NULL);
			}
		    
			void		GlGraphicsDevice::SwapBuffers()
			{
				currentWindow->SwapBuffer();
			}
		   
			void		GlGraphicsDevice::Clear      ()
			{
				
				for(map<string,OGLRenderContext*>::iterator RCI = GLContexts.begin(); RCI != GLContexts.end(); ++RCI )
				{
		           delete RCI->second;
				}
				
				

				for(map<string, Window*>::iterator WI = windows.begin(); WI!=windows.end(); ++WI )
				{
			       delete WI->second;
					
				}
				
			 	delete resourceContext ;
				delete resourceWindow;
				
				resourceContext = 0;
				resourceWindow  = 0;
                ChangeDisplaySettings ( NULL, 0 );


			}

			
			bool        GlGraphicsDevice::GlInit     ()
			{
				initExtensions();
				glClearColor	(0.0f, 0.0f, 0.0f, 1.0f);
				
			
				
				return true;
			}

			
			RenderContext	*		 GlGraphicsDevice::GetRendererContext	() const
			{
				if(currentContext!=0)
				{
				return currentContext;
				}
				return 0;
			}
	       
			RenderState	    *		         GlGraphicsDevice::GetRendererState	    () const 
			{
				return currentContext->GetRenderState();
			}
			Camera *				         GlGraphicsDevice::GetCamera		    () const 
			{
				return camera;
			}
            Window	*	GlGraphicsDevice::GetWindow		() const 
			{
				if(currentWindow!=0)
				{
				return currentWindow;
				}
				return 0;
			}

				
			int GlGraphicsDevice::glMinorVersion = 0;
			    
			int GlGraphicsDevice::glMadjorVersion = 0;
	}
}
